package qwfws

import (
	"context"
	"encoding/json"
	"errors"
	"log/slog"
	"net/url"
	"time"

	"gitee.com/gxcc/go-utils/utils"

	"gitee.com/gxcc/go-thirds/cache"
	"gitee.com/gxcc/go-thirds/qw"
)

// QYWXSuiteApi 企业微信服务商应用
type QYWXSuiteApi struct {
	SuiteId     string `json:"suite_id"`
	SuiteSecret string `json:"suite_secret"`
}

// SaveSuiteTicket 保存SuiteTicket
func (w *QYWXSuiteApi) SaveSuiteTicket(suiteTicket string) {
	var ctx = context.Background()
	var accessTokenRedisKey = "qywx_suite_ticket_" + w.SuiteId
	cache.RedisClient.Set(ctx, accessTokenRedisKey, suiteTicket, 0)
	return
}

func (w *QYWXSuiteApi) GetSuiteTicket() string {
	var ctx = context.Background()
	var accessTokenRedisKey = "qywx_suite_ticket_" + w.SuiteId
	str, _ := cache.RedisClient.Get(ctx, accessTokenRedisKey).Result()
	return str
}

func (w *QYWXSuiteApi) reloadAccessToken() (string, error) {
	var reqUrl = "https://qyapi.weixin.qq.com/cgi-bin/service/get_suite_token"
	var params = map[string]any{
		"suite_id":     w.SuiteId,
		"suite_secret": w.SuiteSecret,
		"suite_ticket": w.GetSuiteTicket(),
	}
	data, _ := json.Marshal(params)
	slog.Debug("reloadAccessToken", "requestData", string(data))

	res, err := utils.HttpPost(reqUrl, nil, data)
	if err != nil {
		slog.Error(err.Error())
		return "", err
	}

	type Info struct {
		Errcode          int    `json:"errcode"`
		Errmsg           string `json:"errmsg"`
		SuiteAccessToken string `json:"suite_access_token"`
		ExpiresIn        int    `json:"expires_in"`
	}
	var info Info
	if err := json.Unmarshal(res.Body, &info); err != nil {
		slog.Error(err.Error())
		return "", err
	}
	if len(info.SuiteAccessToken) == 0 {
		var err = errors.New("获取企业微信三方应用accessToken失败，返回结果：" + string(res.Body))
		slog.Error(err.Error())
		return "", err
	}

	// accessToken存放到redis中
	var ctx = context.Background()
	var accessTokenRedisKey = "qywx_suite_access_token_" + w.SuiteId
	cache.RedisClient.Set(ctx, accessTokenRedisKey, info.SuiteAccessToken, time.Duration(info.ExpiresIn-60)*time.Second)

	slog.Debug("reloadAccessToken", "accessToken", info.SuiteAccessToken)
	return info.SuiteAccessToken, nil
}

// GetAccessToken 获取AccessToken
func (w *QYWXSuiteApi) GetAccessToken() (string, error) {
	var ctx = context.Background()
	var accessTokenRedisKey = "qywx_suite_access_token_" + w.SuiteId
	accessToken, err := cache.RedisClient.Get(ctx, accessTokenRedisKey).Result()
	if err != nil || len(accessToken) == 0 {
		return w.reloadAccessToken()
	}
	return accessToken, nil
}

// GetPreAuthCode 获取预授权码
// 该API用于获取预授权码。预授权码用于企业授权时的第三方服务商安全验证。
func (w *QYWXSuiteApi) GetPreAuthCode() (string, int, error) {
	accessToken, _ := w.GetAccessToken()
	reqUrl := "https://qyapi.weixin.qq.com/cgi-bin/service/get_pre_auth_code?suite_access_token=" + accessToken

	res, err := utils.HttpGet(reqUrl, nil)
	if err != nil {
		slog.Error(err.Error())
		return "", 0, err
	}

	type Info struct {
		Errcode     int    `json:"errcode"`
		Errmsg      string `json:"errmsg"`
		PreAuthCode string `json:"pre_auth_code"`
		ExpiresIn   int    `json:"expires_in"`
	}

	var info Info
	if err := json.Unmarshal(res.Body, &info); err != nil {
		slog.Error(err.Error())
		return "", 0, err
	}
	if len(info.PreAuthCode) == 0 {
		return "", 0, errors.New("GetPreAuthCode失败，返回结果：" + string(res.Body))
	}

	return info.PreAuthCode, info.ExpiresIn, nil
}

// SetSessionInfo 设置授权配置
// 该接口可对某次授权进行配置。可支持测试模式（应用未发布时）。
func (w *QYWXSuiteApi) SetSessionInfo(PreAuthCode string, authType int) error {
	accessToken, _ := w.GetAccessToken()
	reqUrl := "https://qyapi.weixin.qq.com/cgi-bin/service/set_session_info?suite_access_token=" + accessToken

	var reqParams = map[string]any{
		"pre_auth_code": PreAuthCode,
		"session_info": map[string]any{
			"appid":     []int{},
			"auth_type": authType,
		},
	}
	reqData, _ := json.Marshal(reqParams)

	res, err := utils.HttpPost(reqUrl, nil, reqData)
	if err != nil {
		slog.Error(err.Error())
		return err
	}

	type Info struct {
		Errcode int    `json:"errcode"`
		Errmsg  string `json:"errmsg"`
	}

	var info Info
	if err := json.Unmarshal(res.Body, &info); err != nil {
		slog.Error(err.Error())
		return err
	}
	if info.Errcode != 0 {
		return errors.New("SetSessionInfo失败，返回结果：" + string(res.Body))
	}

	return nil
}

// GetPermanentCode 获取企业永久授权码
// 该API用于使用临时授权码换取授权方的永久授权码，并换取授权信息、企业access_token，临时授权码一次有效。建议第三方以userid为主键，来建立自己的管理员账号。
func (w *QYWXSuiteApi) GetPermanentCode(authCode string) (PermanentCodeResult, error) {
	accessToken, _ := w.GetAccessToken()
	reqUrl := "https://qyapi.weixin.qq.com/cgi-bin/service/get_permanent_code?suite_access_token=" + accessToken

	var reqParams = map[string]any{
		"auth_code": authCode,
	}
	reqData, _ := json.Marshal(reqParams)

	res, err := utils.HttpPost(reqUrl, nil, reqData)
	if err != nil {
		slog.Error(err.Error())
		return PermanentCodeResult{}, err
	}

	var result PermanentCodeResult
	if err := json.Unmarshal(res.Body, &result); err != nil {
		slog.Error(err.Error())
		return PermanentCodeResult{}, err
	}
	if result.Errcode != 0 {
		return PermanentCodeResult{}, errors.New("GetPermanentCode失败，返回结果：" + string(res.Body))
	}

	return result, nil
}

// GetAuthInfo 获取企业授权信息
// 该API用于通过永久授权码换取企业微信的授权信息。 永久code的获取，是通过临时授权码使用get_permanent_code 接口获取到的permanent_code。
func (w *QYWXSuiteApi) GetAuthInfo(authCorpId, permanentCode string) (AuthInfoResult, error) {
	accessToken, _ := w.GetAccessToken()
	reqUrl := "https://qyapi.weixin.qq.com/cgi-bin/service/get_auth_info?suite_access_token=" + accessToken

	var reqParams = map[string]any{
		"auth_corpid":    authCorpId,
		"permanent_code": permanentCode,
	}
	reqData, _ := json.Marshal(reqParams)

	res, err := utils.HttpPost(reqUrl, nil, reqData)
	if err != nil {
		slog.Error(err.Error())
		return AuthInfoResult{}, err
	}

	var result AuthInfoResult
	if err := json.Unmarshal(res.Body, &result); err != nil {
		slog.Error(err.Error())
		return AuthInfoResult{}, err
	}
	if result.Errcode != 0 {
		return AuthInfoResult{}, errors.New("GetAuthInfo失败，返回结果：" + string(res.Body))
	}

	return result, nil
}

// GetCorpToken 获取企业access_token
// 第三方服务商在取得企业的永久授权码后，通过此接口可以获取到企业的access_token。
// 获取后可通过通讯录、应用、消息等企业接口来运营这些应用。
func (w *QYWXSuiteApi) GetCorpToken(authCorpId, permanentCode string) (string, error) {
	var ctx = context.Background()
	var cacheKey = "qywx_access_token_" + authCorpId
	corpAccessToken, err := cache.RedisClient.Get(ctx, cacheKey).Result()
	if len(corpAccessToken) > 0 {
		return corpAccessToken, nil
	}

	accessToken, _ := w.GetAccessToken()
	reqUrl := "https://qyapi.weixin.qq.com/cgi-bin/service/get_corp_token?suite_access_token=" + accessToken

	var reqParams = map[string]any{
		"auth_corpid":    authCorpId,
		"permanent_code": permanentCode,
	}
	reqData, _ := json.Marshal(reqParams)

	res, err := utils.HttpPost(reqUrl, nil, reqData)
	if err != nil {
		slog.Error(err.Error())
		return "", err
	}

	type Result struct {
		Errcode     int    `json:"errcode"`
		Errmsg      string `json:"errmsg"`
		AccessToken string `json:"access_token"`
		ExpiresIn   int    `json:"expires_in"`
	}

	var result Result
	if err := json.Unmarshal(res.Body, &result); err != nil {
		slog.Error(err.Error())
		return "", err
	}
	if result.Errcode != 0 {
		return "", errors.New("GetCorpToken失败，返回结果：" + string(res.Body))
	}

	// 缓存到redis中
	cache.RedisClient.Set(ctx, cacheKey, result.AccessToken, time.Duration(result.ExpiresIn-60)*time.Second)

	return result.AccessToken, nil
}

// GetAdminList 获取应用的管理员列表
// 第三方服务商可以用此接口获取授权企业中某个第三方应用的管理员列表(不包括外部管理员)，以便服务商在用户进入应用主页之后根据是否管理员身份做权限的区分。
func (w *QYWXSuiteApi) GetAdminList(authCorpId string, agentId int64) ([]qw.Admin, error) {
	accessToken, _ := w.GetAccessToken()
	reqUrl := "https://qyapi.weixin.qq.com/cgi-bin/service/get_admin_list?suite_access_token=" + accessToken

	var reqParams = map[string]any{
		"auth_corpid": authCorpId,
		"agentid":     agentId,
	}
	reqData, _ := json.Marshal(reqParams)

	res, err := utils.HttpPost(reqUrl, nil, reqData)
	if err != nil {
		slog.Error(err.Error())
		return nil, err
	}

	type Info struct {
		Errcode int        `json:"errcode"`
		Errmsg  string     `json:"errmsg"`
		Admin   []qw.Admin `json:"admin"`
	}

	var info Info
	if err := json.Unmarshal(res.Body, &info); err != nil {
		slog.Error(err.Error())
		return nil, err
	}
	if info.Errcode != 0 {
		return nil, errors.New("GetAdminList失败，返回结果：" + string(res.Body))
	}

	return info.Admin, nil
}

// GetWebAuthPageUrl 构造第三方oauth2链接
// 如果第三方应用需要在打开的网页里面携带用户的身份信息，第一步需要构造如下的链接来获取code
// scope: 应用授权作用域。
//
//	snsapi_base：静默授权，可获取成员的基础信息（UserId与DeviceId）；
//	snsapi_userinfo：静默授权，可获取成员的详细信息，但不包含手机、邮箱等敏感信息；
//	snsapi_privateinfo：手动授权，可获取成员的详细信息，包含手机、邮箱等敏感信息。
func (w *QYWXSuiteApi) GetWebAuthPageUrl(redirectUri, scope string) string {
	var pageUrl = "https://open.weixin.qq.com/connect/oauth2/authorize"
	var values = url.Values{}
	values.Add("appid", w.SuiteId)
	values.Add("redirect_uri", redirectUri)
	values.Add("response_type", "code")
	values.Add("scope", scope)
	values.Add("state", utils.GenRandomString(12))
	pageUrl += "?" + values.Encode() + "#wechat_redirect"
	return pageUrl
}

// GetAuthUserInfo3rd 第三方根据code获取企业成员信息
func (w *QYWXSuiteApi) GetAuthUserInfo3rd(code string) (qw.UserAuthResult, error) {
	accessToken, _ := w.GetAccessToken()
	reqUrl := "https://qyapi.weixin.qq.com/cgi-bin/service/getuserinfo3rd" +
		"?suite_access_token=" + accessToken +
		"&code=" + code

	res, err := utils.HttpGet(reqUrl, nil)
	if err != nil {
		slog.Error(err.Error())
		return qw.UserAuthResult{}, err
	}

	var result qw.UserAuthResult
	if err := json.Unmarshal(res.Body, &result); err != nil {
		slog.Error(err.Error())
		return qw.UserAuthResult{}, err
	}
	if result.Errcode != 0 {
		return qw.UserAuthResult{}, errors.New("GetAuthUserInfo3rd失败，返回结果：" + string(res.Body))
	}

	return result, nil
}

// GetUserDetail3rd 第三方根据code获取企业成员信息
func (w *QYWXSuiteApi) GetUserDetail3rd(userTicket string) (qw.UserDetailResult, error) {
	accessToken, _ := w.GetAccessToken()
	reqUrl := "https://qyapi.weixin.qq.com/cgi-bin/service/getuserdetail3rd" +
		"?suite_access_token=" + accessToken

	var reqParams = map[string]any{
		"user_ticket": userTicket,
	}
	reqData, _ := json.Marshal(reqParams)

	res, err := utils.HttpPost(reqUrl, nil, reqData)
	if err != nil {
		slog.Error(err.Error())
		return qw.UserDetailResult{}, err
	}

	var result qw.UserDetailResult
	if err := json.Unmarshal(res.Body, &result); err != nil {
		slog.Error(err.Error())
		return qw.UserDetailResult{}, err
	}
	if result.Errcode != 0 {
		return qw.UserDetailResult{}, errors.New("GetUserDetail3rd失败，返回结果：" + string(res.Body))
	}
	return result, nil
}
